Handle_Map inside a Sparse_Set
Sparse_Set :: struct($T: typeid, $HANDLE_TYPE: typeid, $LEN: u64 = 10) {
sparse: [LEN]HANDLE_TYPE, // ent_handle = sparse[ent]
dense: [dynamic]Key, // ent = dense[ent_handle]
data: hm.Handle_Map(T, HANDLE_TYPE), // T = hm.get(&sparse_set.data, ent_handle)
}
-
E -> Handle -> Tis slower thanE -> dense_idx -> T. -
Gives more safety, I think, for having something like generations.
-
This is a bad idea, as it defeats the whole purpose of having the data packed with a
Sparse_Set... -
There's no reason to use this.
Thinking in terms of valid data when the backing storage changes (add/remove/move/resize)
-
For
Timer:-
A
Timeris equivalent tof32; it's atomic. -
All timers could share a system: they are all updated via calling
updateon them. This means that I have reasons to store them together, even tho this is not a requirement.-
After further thought, maybe not all timers should be updated equally, as some might want high update precision for visuals, while others are for network or physics.
-
Considering the division between processing of data, there could be
_timers_visuals/_timers_physics, symbolizing the moment when each of these components should have the system update called.
-
-
No timers are equal: a specific timer is wanted for a system; there's a concept of "timer types", where a timer is used for AI, or UI, or visuals, etc. Every timer can have a completely different purpose.
-
An entity could "have" many timers, and a system wants a specific timer from all timers available.
-
"This timer belongs to who/what?"
-
Used for :
-
Damage_Indicator -
AI_State_Machine-
This would not be how the AI is used. Utility AI would evaluate the next decision based on the aggregated scores.
-
-
-
-
For
Sprite:-
A
Spriteis equivalent tof32; it's atomic. -
Thinking in terms of shared processing:
-
All timers do not share a system.
-
The sources say that storing atomic data inside an array doesn't make sense, as grouping things together should have semantic meaning. I go a step further and say it should indicate data that is processed together.
-
This indicates that a
[]Spriteor_sprites: Component(Sprite)doesn't make sense, as all sprites are not processed together.
-
-
There should have specialization in these arrays/components, such as creating new arrays or components for each diference in semantic/processing.
-
"This sprite belongs to who/what?"
-
Is more about having a reference to a
Sprite. -
Used for :
-
Damage_Indicator -
shadows sprites.
-
projectile sprite.
-
-
-
Sprite-
The projectile's sprite could be the "main transform" for that object, but currently I have no good way to query for the sprite.
-
Maybe sprites should be a
Sparse_Set, just likeDynamic_Body,AreaandTransform2_Node(potentially). -
Damage_Indicator-
Wants a "pointer" to a Sprite.
-
-
The.Spriteis an entity-
Problem :
-
Queries:
-
The sprite would be an entity it self, so queries like this wouldn't be possible:
for entity, sprite, vel_max, entity_target, damage in sparse.intersect_next(&rd.sprites, &_velocities_max, &_entities_target, &_damages) { move_to_target_or_damage_target_and_self_destroy(entity, sprite, vel_max^, entity_target^, damage^, world, dt) }-
The entity that has
&_velocities_max, &_entities_target, &_damagesis not the same that has a&rd.sprite.-
A ENTITY_SPRITE uses the
rd.sprite, while the ENTITY_CHARACTER/ENTITY_CREATURE/ENTITY_PROJECTILE uses the other components. -
So, what I mean is, this is a data anomaly: the sprite used will be incorrect.
-
-
"If a 'thing' is an entity, it cannot be considered a 'component' for another entity".
-
The only way for an entity to also be considered a component, would be if when querying for the components, the
_childrencomponent is checked. This would require a recursive check, where not only the entities fromc0.densewould have to be checked, but also the children of all these entities, recursively. -
If I don't want this behavior, I'd have to not use the
_childrensparse set, ensuring that "a 'thing' is either a component OR a entity, not both".
-
-
-
-
Only
Transform2_Nodeis a component.Sprite_Data :: struct { debug_name: string, // Tex tex_path: string, tex_extent: spatial.Extent, tex_idx: u32, // Mesh mesh: Mesh, model_matrix: matrix[4, 4]f32, source: spatial.Recti, flip_x: bool, flip_y: bool, tile_size: [2]int, tile_idx: int, _total_tiles: int, color_mult: [4]f32, // 0 to 1. color_add: [4]f32, // 0 to 1. } sprite_add :: proc( pos: [2]f32 = {}, rot: f32 = 0, scale: [2]f32 = { 1, 1 }, parent: sparse.Key = {}, ) -> (sprite: Sprite) { entity = _entity_count _entity_count += 1 spatial.trans2_add(entity, pos, parent = parent) } tex, tex_idx := rd.texture_get_from_cache_or_append(cmd, &level.textures, "assets/char.png", arena) sprite_entity := rd.sprite_add( cmd = cmd, debug_name = "char", texture = tex, texture_idx = tex_idx, pos = { 0, 0 }, parent = dynamic_body.trans, origin = { 16, 31 }, arena = arena, ) sparse.add_as_child(&_children, entity, sprite_entity) rd.layers_add_draw_option(&level.layers, sprite_entity, 10, true, arena.cpu_alloc) -
Everything is a component:
Transform2_Node+Texture+Mesh+Material2Dare components.-
Mesh: -
Material2D:
Material2D :: struct { // Tex thing tex_idx: u32, // UV source: spatial.Recti, flip_x: bool, flip_y: bool, tile_size: [2]int, tile_idx: int, _total_tiles: int, // Frag color_mult: [4]f32, // 0 to 1. color_add: [4]f32, // 0 to 1. } sprite_create :: proc( pos: [2]f32 = {}, rot: f32 = 0, scale: [2]f32 = { 1, 1 }, parent: sparse.Key = {}, ) -> (sprite: Sprite) { entity = _entity_count _entity_count += 1 spatial.trans2_add(entity, pos, parent = parent) sparse.add(&rd.meshes, entity, /* mesh info */) sparse.add(&rd.textures, entity, /* texture info */) // If using an existing texture, this has to be implemented in a sparse set so this is allowed. sparse.add(&rd.material2d, entity, /* material2d info */) } -
-
-
The data is stored in an
Handle_Map(T), using.handle: Handleto access the data, without any components.-
This requires the handle to be stored.
-
Check the strategy "
Sparse_Setto aHandleto aHandle_Map: "Handle to Handle" / "Pointer to Pointer"".
-
-
-
The data is stored in a singleComponent(T), using other components to help differentiate the "type ofT"-
How would I differentiate "bounciness"?
-
Maybe there's a "Bounce component", specifying the "max angle" and "base speed".
-
-
Problems :
-
If an entity has a "shadow sprite" and a "main sprite", there's no way to distinguish between them while using a single data structure. Adding extra components to the same entity, for example, "bouncy component" doesn't help differentiate the sprites while querying.
-
This is where these ideas come from:
-
"Many
[]T/map[]T/Handle_Map/Component" -
"The
Spriteis an entity" -
"What makes
Tdifferent from one another is stored insideT, and not related to the data structure"
-
-
-
-
Many.[]T/map[]T/Handle_Map/Component-
This is a way to tackle specialization; having many storage structures doesn't solve the issue of things changing inside these backing structures.
-
Killer Problem :
-
This is really weird for trying to hold onto a reference of
T, as you don't know which of many data structures the data is in.-
This is where the main problem of
Spriteoriginated.
-
-
-
-
What makes.Tdifferent from one another is stored insideT, and not related to the data structure-
This is reaaaally weird, as one could argue that I could have an array of
f32and that it's the job of thef32to indicate how it should be used.
-
-
-
Transform2_Node-
Before the changes to
Transform2_Node, I could store thespatial.Transform2_Nodeinside aSparse_Set, but now this is no longer possible as the transforms are stored inside aHandle_Mapinside the spatial library. -
I'd like to reconsider that maybe instead of a
Handle_Map, I could use aSparse_Set.-
If I want to query this component with other things, I'd have to store the transform with the entity_key.
-
-> If you think about it, it's kinda weird that
Transform2_Nodeis not a component.... -
Well for what I revised, it doesn't really seem to be room for letting the
Transform2_Nodebe a component, even tho it probably should...-
The.Body/Spriteis an entity, storing theTransform2_Nodeas aSparse_Set-
This is probably the only option.
-
-
TheBody/Spritestores anentity_keyfor aTransform2_Node; theTransform2_Nodeis stored in aSparse_Setbut the key is not provided; used just like ahm.add.-
This doesn't allow for queries and creates unsafer code.
-
-
The.Body/Spritereceives a foreignentity_keyto add data to theTransform2_Nodesparse set-
This shots it self in the foot, basically.
-
-
-
-
It's about having a reference to a
Transform2_Node, used by parent/children relationships. -
Currently it uses pointers.
-
There's other keys available, besides the Entity, via:
-
Wherever specialization is needed, a new Entity is used.-
This possibility was explored in the "The data is stored in an
Component(T), usingentityto access the data" idea below, and discarded as not being good.
-
-
A
structstores a key to the data:-
The data is stored in an
Handle_Map(T), using.handle: Handleto access the data, without any components-
This is just a way to have
transforms[idx], but with a generational system . -
When something is deleted or removed from the
Handle_Map, the elements in the internal array are not moved, so other handles are not invalidated. -
A
Handle_Mapis a way to have a generic array (stack or heap) with ways to check if the entry in the array is valid. It does not care much about cache efficiency. -
It's possible to remove the necessity of having a handle inside of
T.-
The
Handle_Mapstores an array of handles that match the required handle for a get.
-
-
How does this solve the current issue:
-
In summary, the
Spritedoesn't hold a POINTER to a transform anymore, but a HANDLE. -
When a
Dynamic_Bodymoves inside the_dynamic_bodiescomponent:-
Previously that would move that Transform2_Node as well, as
Body :: struct { using trans: Transform2_Node }, and asSprite.parentstores a pointer to that, moving the source would cause corrupted behavior. -
Now, the data is no longer inside the
Body, but inside aspatial.transforms, which makes so that when theBodymoves, theSprite.parentdoesn't care.
-
-
-
-
The data is stored in an[]TorComponent(T), using.ptr_to_t: ^Tto access the data, with or without using components-
Immediate killer problem when the underlying storage structure changes.
-
-
The data is stored in an.[]T, using.idx_to_t: u32to access the data, without any components-
Immediate killer problem when the underlying storage structure changes.
-
It's just like handles, but without a generational index and some useful features that remember a
Small_Array; there's no tracking of available slots. -
Seems like the handle idea is better.
-
-
The data is stored in an[]T, using.idx_to_t: u32and_indices_to_t: Component(u32)to access the data-
Immediate killer problem when the underlying storage structure changes.
-
-
The data is stored in aComponent(T), using.dense_idx: u32to access the data-
Immediate killer problem when the underlying storage structure changes.
-
-
map:key-
I prefer Sparse Sets (Components).
-
-
-
The data is indirectly stored inside a manager:-
Stored inside a.Component(Transform_Storage)-
This doesn't solve the original problem of getting valid data; it only groups similar things together.
-
-
Through aTransform_System-
I would need a specific transform, and that I cannot get when all data is inside the
Transform_System. This doesn't solve the problem at all, only creates an extra layer of indirection.
-
-
-
-
If the only ACTUAL key available is the Entity, and I don't store this key anywhere:-
Many Array/Map/Handle_Map/Component-
This is a way to tackle specialization; having many storage structures doesn't solve the issue of things changing inside these backing structures.
-
-
A:structstores the data itself-
I'd have to store pointers to the transform of other entities, while their transform for sure is being stored inside an array which changes. This is not possible.
-
-
The data is stored in anComponent(T), usingentityto access the data.-
The original sparse set design.
-
This allows for all cache being updated on a single system.
-
This could be weird if the parent changes between updates, for sure.
-
-
Indirection:
There's an extra level of indirection where you have to go through the entity and check if it exists, and then check if this entity has a transform component.-
This is true, BUT this is also true for
Handle_Map:
trans2_node :: proc(trans: Transform2_Node_Handle) -> (trans_node: ^Transform2_Node) { trans_node = hm.get(&transforms2, trans) if trans_node == nil { // error: not found return nil } return trans_node }-
For
Sparse_Setthat would be:
trans2_node :: proc(trans: sparse.Key) -> (trans_node: ^Transform2_Node) { trans_node, found := sparse.get(&transforms, trans) if !found { // error: not found return nil } return trans_node }-
It's the same level of indirection.
-
-
Problem :
-
"If a 'thing' is an entity, it cannot be considered a 'component' for another entity".
-
Check the explanation for
Sprite.
-
-
The.Body/Spriteis an entity, storing theTransform2_Nodeas aSparse_Settransforms: ecs.Component(Transform2_Node) Transform2_Node :: struct { using local: Transform2, parent_entity: sparse.Key, } trans2_add :: proc(entity: sparse.Key, pos: [2]f32 = {}, rot: f32 = 0, scale: [2]f32 = { 1.0, 1.0 }, parent: sparse.Key = {}) { assert(scale != 0) trans_node: Transform2_Node trans_node.pos = pos trans_node.rot = rot trans_node.scale = scale trans_node.parent = parent sparse.add(&transforms, entity, trans_node) }-
For
Body:Body_Data :: struct { handle: ^jolt.Body, id: jolt.Body_ID, layers: u32, is_sensor: bool, shape: Shape, queued_changes: [dynamic]Body_Queue_Change, } // this body has the components: 'trans2', 'Body_Data' _body_create :: proc(pos: [2]f32, parent: sparse.Key) -> (entity: sparse.Key) { entity = _entity_count _entity_count += 1 spatial.trans2_add(entity, pos, parent = parent) sparse.add(&bodies_data, entity, Body_Data{ // usual things here }) return }-
Atomicity:
-
Body is ALREADY ATOMIC, so breaking it apart might give meaningless queries.
-
"A body is an entity that has a
Transform2_Nodecomponent and aBody_Datacomponent". -
Other components could make sense for a body, such as
velocity, which would define a "dynamic body". Those might not actually be "components", but the ACTUAL DEFINITION of a body.
-
-
-
For
Sprite:Sprite_Data :: struct { debug_name: string, // Tex tex_path: string, tex_extent: spatial.Extent, tex_idx: u32, // Mesh mesh: Mesh, model_matrix: matrix[4, 4]f32, source: spatial.Recti, flip_x: bool, flip_y: bool, tile_size: [2]int, tile_idx: int, _total_tiles: int, color_mult: [4]f32, // 0 to 1. color_add: [4]f32, // 0 to 1. } sprite_create :: proc( pos: [2]f32 = {}, rot: f32 = 0, scale: [2]f32 = { 1, 1 }, parent: sparse.Key = {}, ) -> (sprite: Sprite) { entity = _entity_count _entity_count += 1 spatial.trans2_add(entity, pos, parent = parent) }-
Atomicity:
-
It doesn't make sense to separate the
TextureorMesh, as the sprite loses all its meaning without both of them. The rest also only has meaning if in the context of a sprite. -
"A sprite is an entity that has a
Transform2_Nodecomponent and aSprite_Datacomponent".
-
-
-
-
TheBody/Spritestores anentity_keyfor aTransform2_Node; theTransform2_Nodeis stored in aSparse_Setbut the key is not provided; used just like ahm.add.-
Rationale
-
I could query for
Transform2_Nodedata, I think.
-
-
Problem :
-
Queries:
-
The query will use an "Entity Key" for intersecting
Sparse_Sets, BUT, in this circumstance, theTransform2_Nodeuses a "Transform Key", as this key is not one from an entity, but one generated onsparse.add/"sparse.append". Any query performed with different "keys" are incompatible.-
Example:
-
An entity
5might have a sprite sparse set, and this sprite has the "transform_key"8; when querying(&_sprites, &spatial.transforms), this will be highly incorrect, as the query will return the "sprite 5" and "transform 5". This query gives incorrect information.
-
-
-
So: if using
Sparse Sets this way doesn't give me the query benefit, this strategy gives me nothing. -
Sure, the data is more "packed", but at the cost of confusion in the design, as I could perform a query, but that would be incorrect, enabling mistakes if I'm not very aware of what the "transform_key" means.
-
-
-
-
The.Body/Spritereceives a foreignentity_keyto add data to theTransform2_Nodesparse settransforms: ecs.Component(Transform2_Node) Transform2_Node :: struct { using local: Transform2, parent_entity: sparse.Key, } trans2_add :: proc(entity: sparse.Key, pos: [2]f32 = {}, rot: f32 = 0, scale: [2]f32 = { 1.0, 1.0 }, parent: sparse.Key = {}) { assert(scale != 0) trans_node: Transform2_Node trans_node.pos = pos trans_node.rot = rot trans_node.scale = scale trans_node.parent = parent sparse.add(&transforms, entity, trans_node) }Body :: struct { using trans: sparse.Key, handle: ^jolt.Body, id: jolt.Body_ID, layers: u32, is_sensor: bool, shape: Shape, queued_changes: [dynamic]Body_Queue_Change, } // TODO: what would this look like? _body_create :: proc(entity_key: sparse.Key, pos: [2]f32, parent: spatial.Transform2_Node_Handle) -> (body: Body) { spatial.trans2_add(entity_key, pos, parent = parent) }-
Problem :
-
The key received for
_body_createis most likely a key used for some other entity (if this is not the case, then this falls within the idea "TheBody/Spriteis an entity, storing theTransform2_Nodeas aSparse_Set"), but this means that some problems emerge from the same entity having many things added to a sparse set using the same key.-
The solutions investigated were:
-
Wherever specialization is needed, a new Entity is used.
That's all...
-
-
So this means that it all falls down to the idea "The
Body/Spriteis an entity, storing theTransform2_Nodeas aSparse_Set", and so this current idea is discarded.
-
-
-
-
-
-
Thinking in terms of specialization and shared data
-
There's other keys available, besides the Entity, via:
-
Wherever specialization is needed, a new Entity is used.
-
This is a way to keep using Entity as the ONLY available key.
-
It's quite straight forward: need a new key -> create a new entity, which is equivalent to creating a new key.
-
Works well when subobjects have independent lifecycles or lots of data/behavior.
-
Problems :
-
larger entity count and more queries for parent-child walking.
-
-
Considerations :
-
The
Damage_Indicatordoesn't OWN the sprite; it's just a reference from the entity's sprite.
-
-
Use this when the subobject has identity or can exist independently (or if it carries many other components). For small, ephemeral timers itβs overkill.
-
Promoting AI and Hitbox to entities is a valid and common approach when those subsystems need identity, separate lifetime, or many components (including timers). It restores the βhintβ you miss and fits ECS philosophy (composition). Do it selectively: only promote when the subobject has independent behavior; otherwise keep a TimerStorage to avoid entity bloat.
-
What are the possible advantages of having a system like this?
"the entity dies, but the health bar or AI Controller stays behind".-
Is there advantages for having data like this separate?
-
Thing for example, what if many entities use the same AI Controller or Health? (I don't know why, probably doesn't make sense); think of Shaco's Boxes.
this would mean:
| entity (entity_id) | health (entity_id) |
|--------------------|--------------------|
| 1 | 2 |
| 2 | 2 |
| 3 | 2 | -
Is this currently possible?
-
For add:
-
component.densewould have to increase. -
component.datastays the same. -
component.sparse[new_entity] = component.sparse[existing_entity]
-
-
For remove:
-
It would have to use the dense_idx from one entity and iterate over the whole array trying to find other entries that also use this dense_idx to remove them; something like that.
-
-
-
-
-
How to kill sub-entities when the main entity dies?
-
Use children / parent.
-
There could be a component
-
If a component, I would do something like:
| entity (entity_id) | child (entity_id) |
|--------------------|-------------------|
| 1 | 2 |
| 1 | 3 |
| 1 | 4 | -
So I use something like "component_get_all", for example.
-
-
Other data struct that maps one to another.
-
Using a map is basically the same as a component, but less efficient for huge amounts of entities, and incoherent with the current design.
-
Remember, a Component is just a Sparse Set for Entities -> T.
-
-
The entity could be a struct, instead of a
u32.-
This makes the
Entityand every Entity array fatter; it could be a problem, maybe. -
Also, children would require a heap allocated array, or a limit on how many children an entity can have by using a stack array.
-
-
-
-
A:structstores a key to the data-
Wherever this struct is used, it basically indicates a little bit of OOP in this part of the code.
-
Core problem :
-
Exposing direct references (raw pointers or dense indices) into component storage is unsafe because storage can move or be freed.
-
-
The data is stored in an
Handle_Map(T), using.handle: Handleto access the data, without any components.-
A little bit better than an index, as:
-
if something moves inside the array, the index could be referencing something incoherent.
-
Now, at least, you get notified on
get()that the handle used is invalid.
-
-
A little bit better than a pointer, as:
-
If the object origin is freed, accessing or dereferencing the pointer would result in a crash
-
Now, at least, the crash won't happen and you get notified that the handle is invalid.
-
-
Other Problems :
-
A handle has to be stored. Where?
-
A Sparse_Set stores the Handle.
-
This gives the impression of: "Handle to Handle" or "Pointer to Pointer".
-
-
-
Also, not using components make this data not compatible with queries/systems.
-
This is a problem when you consider that querying the components is a way to update all data with same purpose together, improving performance and avoiding coupling data to objects. Not using components makes that the data is coupled with the struct storing the index.
-
-
A
Timerwould have to have a.handleentry, as for the spec of the handle map library, so the handles can be compared. -
Extra complexity for each fetch.
-
-
Remove :
-
Not using components would cause to lose the notion of lifetime coupling with the entity.
-
The handle has to manually be removed from the handle_map via using its handle.
-
-
-
-
The data is stored in an.[]T, using.idx_to_t: u32to access the data-
This creates an extra indirection, with the sole purpose so the
Damage_Indicatorhave an index to the[]SpriteviaDamage_Indicator.sprite_idx: u32. -
Killer Problems :
-
If something moves inside the array, the index could be referencing something incoherent.
-
Things WILL move as soon as some entity is destroyed, etc; this happens a lot.
-
-
-
Other Problems :
-
Not using components make this data not compatible with queries/systems. This is a problem when you consider that querying the components is a way to update all data with same purpose together, improving performance and avoiding coupling data to objects. Not using components makes that the data is coupled with the struct storing the index.
-
-
Stored inside :
-
Timer library.
-
Level.-
A bit annoying. There could be timers outside the level boundaries, for level transitions, etc.
-
-
Game-
Sure, but timer library might be basically the same thing, while this would have to happen for every game.
-
Letting the library store this is more intuitive.
-
-
Global scope.-
Ugly. Timer library is better.
-
-
-
Remove :
-
Not using components would cause to lose the notion of lifetime coupling with the entity.
-
The element inside that index has to manually be removed from the array via using its index.
-
-
-
-
The data is stored in an.[]T, using.idx_to_t: u32and_indices_to_t: Component(u32)to access the data-
This creates an extra indirection, with the sole purpose to allow the
Damage_Indicatorto also have an index to the[]SpriteviaDamage_Indicator.sprite_idx: u32. So both "character" andDamage_Indicatorhold into the sameu32.
Stored inside :-
Timer library.
-
Level.-
A bit annoying. There could be timers outside the level boundaries, for level transitions, etc.
-
-
Game-
Sure, but timer library might be basically the same thing, while this would have to happen for every game.
Letting the library store this is more intuitive.
-
-
Global scope.-
Ugly. Timer library is better.
-
-
-
Killer Problems :
-
While the "character" uses its
Entityid to access the u32, theDamage_Indicatorusessprite_idx: u32to get the data.-
This creates incoherences between these two methods of accessing the data. The
Entitymethod allows for entities to be moved, removed, etc, while theDamage_Indicatoris stuck with au32giving it direct access to the[]Sprite. In a way, this is a worse/similar version ofDamage_Indicator.sprite_dense_idx: u32.
-
-
If something moves inside the array, the index could be referencing something incoherent.
-
Things WILL move as soon as some entity is destroyed, etc; this happens a lot.
-
-
-
-
The data is stored in a.Component(T), using.dense_idx: u32to access the data-
Killer Problems :
-
Storing the
dense_idxalone doesn't tell the whole story, as it's not clear WHICH component thisdense_idxrefers to; this is unsafe. -
If components are removed, the
dense_idxcould point to:-
something outside the boundaries of the
.dataarray; -
or something completely different, unexpected.
-
In a way, this manages to have both issues of 'pointer invalidation through a free' AND 'idx invalidation for an array through moving the elements'.
-
-
-
-
The data is stored in an.[]TorComponent(T), using.ptr_to_t: ^Tto access the data, with or without using components-
Killer Problems :
-
Pointers to data inside a
Component(T)should not be stored; which is the case for the return ofsparse.add.-
This pointer is a pointer to a SLOT on the
component.dataarray. If this array is resized, or any other thing happens to the contents of this array, the pointer will keep pointing to the same place, but now the data that was previously there might have changed causing random behavior, or might be invalid, causing a crash. -
This is the same limitation for when using
Handle_Maps. -
This completely prohibits the usage of this strategy.
-
-
-
Annoyances :
-
If the object origin is freed, accessing or dereferencing the pointer would result in a crash.
-
This is not really a problem if coupling the lifetimes of everything.
-
-
-
-
map:key-
I prefer Sparse Sets (Components).
-
Shares the same problems as other storage structures.
-
-
-
The data is indirectly stored inside a manager:-
Stored inside a.Component(Timer_Storage)-
This is better interpreted as a
Component([]Timer). -
This is a way to avoid this:
| entity | health |
|--------|--------|
| 1 | 2 |
| 1 | 3 |
| 1 | 4 | -
Wrong problem :
-
This doesn't solve the original problem of fetching a specific data, it only groups similar things together. If you want a specific data inside that array, the original problem arises again.
-
-
-
Through aTimer_System-
This follows up the design from a Tween.
-
The timer would be added to the timer system, which could be all an entity needs for all its timers.
-
Killer Problems :
-
If the same effect happens very frequently, the timer would not reset, but new ones are created instead and accumulating a lot of
on_endcallbacks. -
In the same topic, a timer cannot be reset or stopped once it has started.
-
This is just a way to avoid the referencing problem by delegating it to a system. It doesn't solve the problem for
Spritefor example. It avoids the "timer type" necessity, it does not solve it.
-
-
-
-
-
If the only ACTUAL key available is the Entity, and I don't store this key anywhere:-
~Many Array/Map/Handle_Map/Component
-
Each new data struct represents semantic and intent, so that way I can specialize the data based on which "array" this is stored.
-
Problems :
-
I think this is a really bad solution, as if I have many specializations, I'd have to have many arrays, with permutations for each specialization.
-
Ex:
-
sprite_bouncy
-
sprite_bouncy_blinkable
-
sprite_blinkable
-
sprite_shadow
-
etc
-
timers_with_type1: Component(Timer)
-
timers_with_type2: Component(Timer)
-
-
For Timers in specific, this seems like a huge waste, as every timer is different from another. I would end up having A LOT of different timer components, where most of them are basically empty, as it might be a very specific timer.
-
-
-
A:structstores the data itself-
This is basically OOPing this part of the code.
-
Killer Problems :
-
The MAIN problem of this strategy is "shared data":
-
For example, the case of a
SpriteforDamage_Indicator, as the sprite is a component from the entity. -
Every attempt to solve this would fall in one of these 2 ideas discussed:
-
"The data is stored in an[]T, while_indices_to_t: Component(u32)"-
It was argued to be a bad idea.
-
-
Astructstores a key to the data
-
Both ideas are simply a DIFFERENT idea. Trying to solve the sharable problem requires a different design to be implemented. This idea cannot be saved when it comes to shared data, it's a problem from the design: something has ownership to the data and others want to access this data.
-
-
-
This breaks the idea of storing all data inside the same data struct, which breaks the idea of all Timers being updated together.
-
For timers, this requires
updateto be called for each of them.-
As a way to avoid this, when initing a timer, it would be added to a:
-
Timer_System, passed as a parameter. -
global process array, inside the
import "shared:eng/timer.
-
-
Regardless, this is not the main problem.
-
-
Not so good of a system, as updating a timer is impossible to enforce, so you end up forgetting sometimes.
-
Not so cache efficient for updates.
-
-
-
-
The data is stored in an.Component(T), usingentityto access the data-
The original sparse set design.
-
Problems :
-
It was from here that the specialization problem was originated.
-
-
-